﻿//-----------------------------------------------------------------------
// <copyright file="CheckinNotes.cs" company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation 2011. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Internal.TeamFoundationServer.Submit
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.DirectoryServices.AccountManagement;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Mail;
    using System.Reflection;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    using Microsoft.TeamFoundation.Build.Client;
    using Microsoft.TeamFoundation.Build.Workflow;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.WpfControls;
    using Properties;

    /// <summary>
    /// Check-in Notes class.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "We will do it in the next version of TFS Submit.")]
    internal partial class CheckinNotes : WizardControlTemplate
    {
        /// <summary>
        /// Pending changes.
        /// </summary>
        private static readonly List<PendingChange> pendingChanges = new List<PendingChange>();

        /// <summary>
        /// Build Number.
        /// </summary>
        private static string buildNumber;

        /// <summary>
        /// List of WI info.
        /// </summary>
        private static WorkItemCheckinInfo[] workItemsCheckinInfo;

        /// <summary>
        /// Workspace object.
        /// </summary>
        private static Workspace workspace;

        /// <summary>
        /// Shelve set name.
        /// </summary>
        private static string shelvesetName;

        /// <summary>
        /// Shelve set description.
        /// </summary>
        private static string shelvesetDescription;

        /// <summary>
        /// Initializes a new instance of the <see cref="CheckinNotes"/> class.
        /// </summary>
        public CheckinNotes()
        {
            try
            {
                Cursor.Current = Cursors.WaitCursor;

                buildNumber = null;

                this.InitializeComponent();

                this.txtEmail.Text = TfsSubmitConfig.CheckinEmailsAliases;

                // Get Problem description.
                this.txtProblem.Text = GetWorkItemDescription();

                // Set the fix with the shelveset Description.
                this.txtFix.Text = shelvesetDescription;

                // Update list of recipients.
                this.SetWorkItemOwnerInTheCheckinMailList();

                // If code review is enabled then this field should be read-only and the info should come form the code review database.
                if (TfsSubmitConfig.CodeReviewEnabled)
                {
                    this.txtCodeReviewers.ReadOnly = true;
                    this.txtCodeReviewers.Text = MalevichDal.GetSignedOffReviewers(shelvesetName, workspace.Name);

                    if (this.txtCodeReviewers.Text.StartsWith(MalevichDal.NeedsWorkConst, StringComparison.OrdinalIgnoreCase))
                    {
                        this.txtCodeReviewers.Text = string.Format(CultureInfo.InvariantCulture, this.txtCodeReviewers.Text, Resources.CheckinNotesNeedsWorkHelpString);
                    }
                }
            }
            finally
            {
                Cursor.Current = Cursors.Default;
            }
        }

        /// <summary>
        /// Sets Workspace object.
        /// </summary>
        public static Workspace Workspace
        {
            set
            {
                workspace = value;
            }
        }

        /// <summary>
        /// Gets PendingChanges object.
        /// </summary>
        public static List<PendingChange> PendingChanges
        {
            get
            {
                return pendingChanges;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether server side check-in.
        /// </summary>
        public static bool ServerSideCheckin { get; set; }

        /// <summary>
        /// Sets Shelve set name.
        /// </summary>
        public static string ShelvesetName
        {
            set
            {
                shelvesetName = value;
            }
        }

        /// <summary>
        /// Sets Shelve set description.
        /// </summary>
        public static string ShelvesetDescription

        {
            set
            {
                shelvesetDescription = value;
            }
        }

        /// <summary>
        /// Sets Work Items Check-in Info object.
        /// </summary>
        public static WorkItemCheckinInfo[] WorkItemsCheckinInfo
        {
            set
            {
                workItemsCheckinInfo = value;
            }
        }

        /// <summary>
        /// Update window.
        /// </summary>
        /// <param name="title">Window Title.</param>
        /// <param name="description">Window Description.</param>
        /// <param name="backButtonEnabled">Back Button Enabled?</param>
        /// <param name="nextButtonEnabled">Next Button Enabled?</param>
        /// <param name="nextButtonCaption">Next Button Caption.</param>
        /// <param name="action">Action to perform on control.</param>
        public override void UpdateWindow(Label title, RichTextBox description, out bool backButtonEnabled, out bool nextButtonEnabled, out string nextButtonCaption, object action)
        {
            backButtonEnabled = true;
            nextButtonEnabled = true;
            nextButtonCaption = Resources.CheckinButtonCaption;

            title.Text = Resources.CheckinNotesTitle;
            description.Text = Resources.CheckinNotesDescription;
        }

        /// <summary>
        /// Continue button event.
        /// </summary>
        public override void NextStep()
        {
            string gatedShelveset = null;

            try
            {
                Cursor.Current = Cursors.WaitCursor;

                // Validate if fields were provided.
                if (!this.InternalValidate())
                {
                    return;
                }

                if (workspace != null)
                {
                    var changeSetId = 0;

                    // Prepare work items to receive check-in information.
                    if (!this.PrepareWorkItem())
                    {
                        return;
                    }

                    try
                    {
                        try
                        {
                            var comment = string.Format(
                                CultureInfo.InvariantCulture,
                                Resources.CheckinCommentString,
                                Environment.NewLine,
                                this.txtProblem.Text,
                                this.txtFix.Text,
                                "TFS Submit");

                            if (this.chkGated.Checked)
                            {
                                var tempShelveset = "GatedShelveset" + Guid.NewGuid().GetHashCode();
                                var shelveset = new Shelveset(workspace.VersionControlServer, tempShelveset, workspace.VersionControlServer.AuthorizedUser)
                                {
                                    CheckinNote = this.GetCheckinNotes(),
                                    WorkItemInfo = workItemsCheckinInfo,
                                    PolicyOverrideComment = Resources.AddinName,
                                    Comment = comment,
                                };

                                workspace.Shelve(shelveset, pendingChanges.ToArray(), ShelvingOptions.Replace);

                                var buildDefinition = GetBuildDefinitionForGatedCheckin();
                                changeSetId = SubmitGatedCheckin("Gated check-in selected." + Environment.NewLine, tempShelveset, buildDefinition);

                                gatedShelveset = tempShelveset + ";" + workspace.VersionControlServer.AuthorizedUser;
                            }
                            else
                            {
                                var policyOverride = new PolicyOverrideInfo(Resources.AddinName, new PolicyFailure[] { });

                                // Check in pending changes.
                                var checkInParameters = new WorkspaceCheckInParameters(pendingChanges.ToArray(), comment)
                                {
                                    Author = workspace.VersionControlServer.AuthorizedUser,
                                    CheckinNotes = this.GetCheckinNotes(),
                                    AssociatedWorkItems = workItemsCheckinInfo,
                                    PolicyOverride = policyOverride,
                                    ValidateAuthor = true,
                                };

                                changeSetId = workspace.CheckIn(checkInParameters);
                            }
                        }
                        catch (GatedCheckinException err)
                        {
                            gatedShelveset = err.ShelvesetName;

                            // It seems system has a gated check in agent enabled.
                            // Check in needs to be done via gated check in process.
                            changeSetId = SubmitGatedCheckin(err.Message + Environment.NewLine + Environment.NewLine, err.ShelvesetName, err.AffectedBuildDefinitions[0].Value);
                        }
                    }
                    catch (VersionControlException err)
                    {
                        MessageBox.Show(
                           err.Message,
                           Resources.ErrorString,
                           MessageBoxButtons.OK,
                           MessageBoxIcon.Error,
                           MessageBoxDefaultButton.Button1,
                           MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
                    }

                    // Check if check in was done.
                    if (changeSetId > 0)
                    {
                        // Delete shelve set.
                        try
                        {
                            workspace.VersionControlServer.DeleteShelveset(shelvesetName, workspace.VersionControlServer.AuthorizedUser);
                        }
                        catch (ShelvesetNotFoundException)
                        {
                            // Ignore - There is nothing we can do here.
                        }
                        catch (InvalidOperationException)
                        {
                            // Ignore - There is nothing we can do here.
                        }

                        // Get 
                        var checkinDescription = this.GetCheckinDescription(changeSetId);

                        // Update work items
                        this.UpdateWorkItem(checkinDescription);

                        // Send email notification
                        this.SendNotification(checkinDescription);
                        
                        MessageBox.Show(
                            Resources.ChangesCheckedInString,
                            Resources.InformationString,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information,
                            MessageBoxDefaultButton.Button1,
                            MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);

                        // Close code review
                        if (TfsSubmitConfig.CodeReviewEnabled)
                        {
                            MalevichDal.CloseCodeReview(shelvesetName);
                        }

                        this.FireGoHomeRequested(null /* action */);
                    }
                }
            }
            finally
            {
                if (!string.IsNullOrEmpty(gatedShelveset))
                {
                    // Delete Gated shelve set
                    var shelveSetName = gatedShelveset.Split(';');
                    try
                    {
                        workspace.VersionControlServer.DeleteShelveset(shelveSetName[0], shelveSetName[1]);
                    }
                    catch (ShelvesetNotFoundException)
                    {
                        // Ignore - There is nothing we can do here.
                    }
                    catch (InvalidOperationException)
                    {
                        // Ignore - There is nothing we can do here.
                    }
                }

                Cursor.Current = Cursors.Default;
            }
        }

        /// <summary>
        /// The get build definition for gated check-in.
        /// </summary>
        /// <returns>The <see cref="Uri"/>.</returns>
        private static Uri GetBuildDefinitionForGatedCheckin()
        {
            // Get Build Server object.
            var buildServer = workspace.VersionControlServer.TeamProjectCollection.GetService<IBuildServer>();

            // Now look for build definitions that are schedule based.
            var buildDefinitions = new List<IBuildDefinition>();

            foreach (var pendingChange in pendingChanges)
            {
                buildDefinitions.AddRange(buildServer.GetAffectedBuildDefinitions(new[] { pendingChange.ServerItem }));
            }

            var bd = buildDefinitions.First(buildDefinition => buildDefinition.TriggerType == DefinitionTriggerType.BatchedContinuousIntegration);
            return bd == null ? null : bd.Uri;
        }

        /// <summary>
        /// Get WorkItem description.
        /// </summary>
        /// <returns>Text with WIs description.</returns>
        private static string GetWorkItemDescription()
        {
            var description = new StringBuilder();
            foreach (var wi in workItemsCheckinInfo)
            {
                description.Append(wi.WorkItem.Id + " - " + wi.WorkItem.Title + Environment.NewLine);
            }

            if (description.Length == 0)
            {
                return Resources.NonWorkItemCheckinString;
            }

            return description.ToString();
        }

        /// <summary>
        /// Get WorkItem description in HTML format.
        /// </summary>
        /// <returns>Text with WIs description in HTML format.</returns>
        private static string GetWorkItemDescriptionInHtml()
        {
            var description = new StringBuilder();
            foreach (var wi in workItemsCheckinInfo)
            {
                description.Append(string.Format(CultureInfo.InvariantCulture, "<a href=\"{0}\">{1}</a> {2} <BR>", GetWorkItemLink(wi.WorkItem), wi.WorkItem.Id, wi.WorkItem.Title));
            }

            if (description.Length == 0)
            {
                return Resources.NonWorkItemCheckinString;
            }

            return description.ToString();
        }

        /// <summary>
        /// Get WorkItem URL.
        /// </summary>
        /// <param name="workItem">Work Item object.</param>
        /// <returns>Returns URL point to Work Item.</returns>
        private static string GetWorkItemLink(WorkItem workItem)
        {
            var tswaClientHyperlinkService = workItem.Project.Store.TeamProjectCollection.GetService<TswaClientHyperlinkService>();
            if (tswaClientHyperlinkService != null)
            {
                Uri uri = tswaClientHyperlinkService.GetArtifactViewerUrl(workItem.Uri);
                if (uri != null)
                {
                    return uri.ToString();
                }
            }

            return null;
        }

        /// <summary>
        /// Get Check in files in HTML format.
        /// </summary>
        /// <returns>String with list of file changes.</returns>
        private static string GetCheckinFilesInHtml()
        {
            var files = new StringBuilder();

            foreach (PendingChange change in pendingChanges)
            {
                files.Append(change.ServerItem + "<BR>");
            }

            return files.ToString();
        }

        /// <summary>
        /// Submit Gated Check-in.
        /// </summary>
        /// <param name="extraMessage">The extra Message.</param>
        /// <param name="shelveset">The shelve set Name.</param>
        /// <param name="buildDefinition">The build Definition. </param>
        /// <returns>Returns change set number.</returns>
        private static int SubmitGatedCheckin(string extraMessage, string shelveset, Uri buildDefinition)
        {
            var changeSetId = 0;

            // Since gated check in can take some minutes, ask user if he is ok with it.
            var dialogResult = MessageBox.Show(
                extraMessage + Resources.LongOperationWarningQuestionString,
                Resources.WarningString,
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Exclamation,
                MessageBoxDefaultButton.Button1,
                MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);

            if (dialogResult == DialogResult.Yes)
            {
                Cursor.Current = Cursors.WaitCursor;

                var queuedBuild = TeamFoundationHelper.SubmitGatedCheckin(workspace.VersionControlServer.TeamProjectCollection, buildDefinition, shelveset);

                if (queuedBuild != null)
                {
                    queuedBuild.WaitForBuildStart();

                    var tswaClientHyperlinkService = workspace.VersionControlServer.TeamProjectCollection.GetService<TswaClientHyperlinkService>();
                    queuedBuild.Build.RefreshAllDetails();
                    var uri = tswaClientHyperlinkService.GetViewBuildDetailsUrl(queuedBuild.Build.Uri);

                    // Open Code Review
                    using (var process = new Process())
                    {
                        process.StartInfo.FileName = uri.ToString();
                        process.Start();
                    }

                    while (queuedBuild.Status == QueueStatus.InProgress)
                    {
                        queuedBuild.Refresh(QueryOptions.Definitions);

                        // We don want spin the processor.
                        Thread.Sleep(1000);
                    }

                    // Without this line the object is not updated.
                    queuedBuild.Build.RefreshAllDetails();

                    if (queuedBuild.Build.Status == BuildStatus.Succeeded || queuedBuild.Build.Status == BuildStatus.PartiallySucceeded)
                    {
                        // Build Succeeded
                        // Get change set id.
                        var changesetSummaries = InformationNodeConverters.GetAssociatedChangesets(queuedBuild.Build);
                        if (changesetSummaries != null && changesetSummaries.Count > 0)
                        {
                            changeSetId = changesetSummaries[0].ChangesetId;

                            // Undo Local changes
                            // bool pendingChangesUpdatedByServer;
                            // workspace.Reconcile(true /* reconcileMissingLocalItems */, out pendingChangesUpdatedByServer);
                            workspace.Undo(pendingChanges.ToArray());
                        }
                    }
                    else
                    {
                        // Build failed.
                        // Show user the error.
                        var errors = new StringBuilder();
                        errors.Append(Resources.ErrorsOccurredMessageString + Environment.NewLine);

                        var buildErrors = InformationNodeConverters.GetBuildErrors(queuedBuild.Build);
                        foreach (var buildError in buildErrors)
                        {
                            errors.Append(buildError.Message + Environment.NewLine);
                        }

                        MessageBox.Show(
                            errors.ToString(),
                            Resources.ErrorString,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error,
                            MessageBoxDefaultButton.Button1,
                            MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
                    }
                }
            }

            return changeSetId;
        }

         /// <summary>
        /// Get Version Build definitions.
        /// </summary>
        /// <returns>Returns a dictionary with version Build definitions.</returns>
        private static Dictionary<string, IBuildDefinition> GetVersionBuildDefinitions()
        {
            // Get Build Server object.
            var buildServer = workspace.VersionControlServer.TeamProjectCollection.GetService<IBuildServer>();

            // Now look for build definitions that are schedule based.
            var buildDefinitions = new List<IBuildDefinition>();
            if (buildServer != null)
            {
                foreach (var pendingChange in pendingChanges)
                {
                    buildDefinitions.AddRange(buildServer.GetAffectedBuildDefinitions(new[] { pendingChange.ServerItem }));
                }

                // If configuration has a build definition name, try to filter it out.
                var versionBuildDefinitions = new Dictionary<string, IBuildDefinition>();
                if (!string.IsNullOrEmpty(TfsSubmitConfig.BuildDefinitionName))
                {
                    foreach (var definition in buildDefinitions.Where(definition => definition.Name.Contains(TfsSubmitConfig.BuildDefinitionName) && !versionBuildDefinitions.ContainsKey(definition.Name)))
                    {
                        // Get specific definition
                        versionBuildDefinitions.Add(definition.Name, definition);
                    }
                }
                else
                {
                    foreach (var definition in buildDefinitions.Where(definition => !versionBuildDefinitions.ContainsKey(definition.Name)))
                    {
                        // Get all definitions
                        versionBuildDefinitions.Add(definition.Name, definition);
                    }
                }

                return versionBuildDefinitions;
            }

            return null;
        }

        /// <summary>
        /// Get current Build number.
        /// </summary>
        /// <returns>Returns a string with the Build number.</returns>
        private static string GetCurrentBuildNumber()
        {
            if (!string.IsNullOrEmpty(buildNumber))
            {
                return buildNumber;
            }

            var versionBuildDefinitions = GetVersionBuildDefinitions();

            foreach (var buildDefinition in versionBuildDefinitions.Values)
            {
                try
                {
                    if (buildDefinition == null)
                    {
                        continue;
                    }

                    // Get last build
                    var spec = buildDefinition.BuildServer.CreateBuildDetailSpec(buildDefinition.TeamProject);
                    spec.MaxBuildsPerDefinition = 5;
                    spec.DefinitionSpec.Name = buildDefinition.Name;
                    spec.QueryOrder = BuildQueryOrder.FinishTimeDescending;
                    spec.InformationTypes = null;
                    var builds = buildDefinition.BuildServer.QueryBuilds(spec).Builds;
                    var buildDetail = builds.Where(build => build.Reason != BuildReason.UserCreated && build.Reason != BuildReason.ValidateShelveset).OrderByDescending(build => build.FinishTime).FirstOrDefault();

                    if (buildDetail == null)
                    {
                        continue;
                    }

                    if (string.IsNullOrEmpty(buildDetail.BuildNumber))
                    {
                        continue;
                    }

                    var buildFormatNumber = string.Empty;
                    if (WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters).ContainsKey("BuildNumberFormat"))
                    {
                        buildFormatNumber = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters)["BuildNumberFormat"] as string;

                        if (string.IsNullOrEmpty(buildFormatNumber))
                        {
                            continue;
                        }
                    }

                    var index = buildFormatNumber.IndexOf("$(Rev:.r)", StringComparison.OrdinalIgnoreCase);
                    if (index == -1)
                    {
                        continue;
                    }

                    var dotsBeforeRevision = buildFormatNumber.Substring(0, index - 1).Split('.').Length;
                    var numbers = buildDetail.BuildNumber.Split('.');
                    var newBuildNumber = new StringBuilder();
                    for (var i = 0; i < numbers.Length; i++)
                    {
                        if (newBuildNumber.Length > 0)
                        {
                            newBuildNumber.Append('.');
                        }

                        // If build has revision, increments the counter to represent that the fix will be available in the next revision.
                        if (i == dotsBeforeRevision)
                        {
                            int rev;
                            if (int.TryParse(numbers[i], out rev))
                            {
                                // Build next version.
                                newBuildNumber.Append(++rev);
                            }
                        }
                        else
                        {
                            newBuildNumber.Append(numbers[i]);
                        }
                    }

                    if (!string.IsNullOrEmpty(buildNumber))
                    {
                        buildNumber += ";  ";
                    }

                    buildNumber += (versionBuildDefinitions.Count > 1 ? buildDefinition.Name + ": " : string.Empty) + newBuildNumber;
                }
                catch (BuildServerException)
                {
                    // Not able to find build server. Build number not available at this moment.
                }
            }

            return buildNumber ?? "N/A";
        }

        /// <summary>
        /// Update work item with check-in information.
        /// </summary>
        /// <param name="description">Check-in description.</param>
        private void UpdateWorkItem(string description)
        {
            // Update work item Info with check in info.
            foreach (var workItemCheckinInfo in workItemsCheckinInfo)
            {
                try
                {
                    // Add check-in information.
                    workItemCheckinInfo.WorkItem.Fields[TfsSubmitConfig.FixDescriptionFieldName].Value = description;
                }
                catch (FieldDefinitionNotExistException)
                {
                    // Not able to find fix description field. Just ignore the exception.
                }

                try
                {
                    workItemCheckinInfo.WorkItem.Save();
                }
                catch (ItemAlreadyUpdatedOnServerException)
                {
                    // ItemAlreadyUpdatedOnServerException
                    // Not able to update WI, we will need to re-update everything.
                    // First re-sync work item.
                    workItemCheckinInfo.WorkItem.SyncToLatest();

                    // Update State Field
                    workItemCheckinInfo.WorkItem.State = TfsSubmitConfig.ResolvedWorkItemState;

                    try
                    {
                        // Update Resolution State
                        workItemCheckinInfo.WorkItem.Fields[TfsSubmitConfig.ResolutionStateFieldName].Value = TfsSubmitConfig.ResolutionStateValue;
                    }
                    catch (FieldDefinitionNotExistException)
                    {
                        // Not able to find Resolution State field. Just ignore the exception.
                    }

                    try
                    {
                        // Update Build Number that fix will be available.
                        var currentbuildNumber = GetCurrentBuildNumber();
                        workItemCheckinInfo.WorkItem.Fields[TfsSubmitConfig.BuildVersionFieldName].Value = currentbuildNumber;
                    }
                    catch (FieldDefinitionNotExistException)
                    {
                        // Not able to find Build number field. Just ignore the exception.
                    }

                    try
                    {
                        // Add check-in information.
                        workItemCheckinInfo.WorkItem.Fields[TfsSubmitConfig.FixDescriptionFieldName].Value = description;
                    }
                    catch (FieldDefinitionNotExistException)
                    {
                        // Not able to find fix description field. Just ignore the exception.
                    }

                    var fields = TfsSubmitConfig.WorkItemFieldNames.Split(';');
                    var values = TfsSubmitConfig.WorkItemFieldValues.Split(';');

                    Debug.Assert(fields.Count() == values.Count(), "WorkItem fields does not match number of work item values");
                    for (var i = 0; i < fields.Count(); i++)
                    {
                        try
                        {
                            workItemCheckinInfo.WorkItem.Fields[fields[i].Trim()].Value = values[i].Trim();
                        }
                        catch (FieldDefinitionNotExistException)
                        {
                            // Not able to find field. Just ignore the exception.
                        }
                    }

                    // Validate if work item has errors, if any error, open Work Item to user fix it.
                    while (workItemCheckinInfo.WorkItem.Validate().Count > 0)
                    {
                        if (!this.FixWorkItem(workItemCheckinInfo))
                        {
                            MessageBox.Show(
                                string.Format(CultureInfo.InvariantCulture, Resources.FailedToUpdateWorkItemString, workItemCheckinInfo.WorkItem.Id),
                                Resources.WarningString,
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Warning,
                                MessageBoxDefaultButton.Button1,
                                MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
                            return;
                        }
                    }

                    workItemCheckinInfo.WorkItem.Save();
                }
            }
        }

        /// <summary>
        /// Validate fields.
        /// </summary>
        /// <returns>Return validation status.</returns>
        private bool InternalValidate()
        {
            var hasErrors = false;
            this.errorProvider.Clear();

            // Validate code review status, if needed.
            if (TfsSubmitConfig.CodeReviewEnabled)
            {
                if (this.txtCodeReviewers.Text.StartsWith(MalevichDal.NeedsWorkConst, StringComparison.OrdinalIgnoreCase))
                {
                    this.errorProvider.SetError(
                        this.lblCodeReviewers,
                        Resources.OpenFeedbackCommentsWarningString);
                    return false;
                }

                // Validate Minimum number of reviewers.
                var names = this.txtCodeReviewers.Text.Split(Utilities.NameSeparator);
                var count = names.Count(name => name.Trim() != Environment.UserName);

                if (count < TfsSubmitConfig.RequiredReviewers)
                {
                    this.errorProvider.SetError(
                        this.lblCodeReviewers,
                        Resources.NotEnoughReviewersWarningString);
                    return false;
                }
            }

            Utilities.ValidateUser(this.txtCodeReviewers, new KeyEventArgs(Keys.Control | Keys.K));
            Utilities.ValidateUser(this.txtTestedBy, new KeyEventArgs(Keys.Control | Keys.K));

            this.txtCCEmail.Text += Utilities.NameSeparator + this.txtCodeReviewers.Text + Utilities.NameSeparator + this.txtTestedBy.Text;

            Utilities.ValidateUser(this.txtEmail, new KeyEventArgs(Keys.Control | Keys.K));
            Utilities.ValidateUser(this.txtCCEmail, new KeyEventArgs(Keys.Control | Keys.K));

            if (this.txtFix.Text.Length == 0)
            {
                this.errorProvider.SetError(this.lblFix, Resources.FixDescriptionNeededWarningString);
                hasErrors = true;
            }

            if (this.txtCodeReviewers.Text.Length == 0)
            {
                this.errorProvider.SetError(this.lblCodeReviewers, Resources.CodeReviewersNeededWarningString);
                hasErrors = true;
            }

            if (this.txtTestedBy.Text.Length == 0)
            {
                this.errorProvider.SetError(this.lblTestedBy, Resources.TestersNeededWarningString);
                hasErrors = true;
            }

            return !hasErrors;
        }

        /// <summary>
        /// Prepare Work Items to receive check-in information.
        /// </summary>
        /// <returns>Return status of the preparation.</returns>
        private bool PrepareWorkItem()
        {
            // Update Bug Info with check-in info.
            foreach (var wi in workItemsCheckinInfo)
            {
                wi.WorkItem.SyncToLatest();

                // Update State Field
                wi.WorkItem.State = TfsSubmitConfig.ResolvedWorkItemState;

                try
                {
                    // Update Resolution State
                    wi.WorkItem.Fields[TfsSubmitConfig.ResolutionStateFieldName].Value = TfsSubmitConfig.ResolutionStateValue;
                }
                catch (FieldDefinitionNotExistException)
                {
                    // Not able to find Resolution State field. Just ignore the exception.
                }

                var fields = TfsSubmitConfig.WorkItemFieldNames.Split(';');
                var values = TfsSubmitConfig.WorkItemFieldValues.Split(';');

                Debug.Assert(fields.Count() == values.Count(), "WorkItem fields does not match number of work item values");
                for (var i = 0; i < fields.Count(); i++)
                {
                    try
                    {
                        wi.WorkItem.Fields[fields[i].Trim()].Value = values[i].Trim();
                    }
                    catch (FieldDefinitionNotExistException)
                    {
                        // Not able to find field. Just ignore the exception.
                    }
                }

                // Validate if work item has errors, if any error, open Work Item to user fix it.
                while (wi.WorkItem.Validate().Count > 0)
                {
                    if (!this.FixWorkItem(wi))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Show WI screen for user manually fix issues.
        /// </summary>
        /// <param name="wi">Work Item object.</param>
        /// <returns>Return the status of the fix. True fixed otherwise false.</returns>
        private bool FixWorkItem(WorkItemCheckinInfo wi)
        {
            using (var workItemFormControl = new WorkItemControl())
            {
                workItemFormControl.Item = wi.WorkItem;
                workItemFormControl.ReadOnly = false;

                using (var bugFormViewer = new WorkItemFormViewer(workItemFormControl))
                {
                    this.Visible = false;
                    bugFormViewer.ShowDialog();

                    // If object was disposed, user clicked in cancel, so this should be dispose as well.
                    // Otherwise make form visible again.
                    if (bugFormViewer.IsDisposed)
                    {
                        this.FireGoHomeRequested(null /* action */);
                    }
                    else
                    {
                        this.Visible = true;

                        // Check to see if WI state is valid (TAG equals VALIDWORKITEM)
                        if (bugFormViewer.Tag as string == WorkItemFormViewer.ValidWorkItem)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Get check-in description.
        /// </summary>
        /// <param name="changeId">Change set id.</param>
        /// <returns>Returns a string with the check-in information.</returns>
        private string GetCheckinDescription(int changeId)
        {
            // Build the check-in description in HTML format.
            var checkinDescription = new StringBuilder();

            // Read HTML resource
            Assembly assembly = Assembly.GetExecutingAssembly();
            Stream stream = assembly.GetManifestResourceStream("Internal.TeamFoundationServer.Submit.CheckinDescription.htm");
            if (stream != null)
            {
                var streamReader = new StreamReader(stream);

                checkinDescription.Append(streamReader.ReadToEnd());

                // Build Version
                checkinDescription = checkinDescription.Replace("![BuildNumber]", GetCurrentBuildNumber());

                // Change set
                Uri changesetLink = TeamFoundationHelper.GetChangeSetUrl(workspace.VersionControlServer.TeamProjectCollection, changeId);
                checkinDescription = checkinDescription.Replace("![ChangeSetLink]", string.Format(CultureInfo.InvariantCulture, "<a href=\"{0}\">{1}</a>", changesetLink, changeId));

                // WorkItems
                checkinDescription = checkinDescription.Replace("![WorkItems]", GetWorkItemDescriptionInHtml());

                // Fix description
                checkinDescription = checkinDescription.Replace("![Fixdescription]", this.txtFix.Text.Replace(Environment.NewLine, "<BR>"));

                // Reviewed by
                if (TfsSubmitConfig.CodeReviewEnabled)
                {
                    const string MalevichUrl = "http://{0}/Malevich/default.aspx?cid={1}";
                    var changeListId = MalevichDal.GetChangeListId(shelvesetName, workspace.Name);
                    checkinDescription = checkinDescription.Replace("![Reviewedby]", this.txtCodeReviewers.Text + "<BR>" + "![ReviewLink]");
                    checkinDescription = checkinDescription.Replace("![ReviewLink]", string.Format(CultureInfo.InvariantCulture, MalevichUrl, TfsSubmitConfig.MalevichServerName, changeListId));
                }
                else
                {
                    checkinDescription = checkinDescription.Replace("![Reviewedby]", this.txtCodeReviewers.Text);
                }

                // Tested by
                checkinDescription = checkinDescription.Replace("![Testedby]", this.txtTestedBy.Text);

                // Testing notes
                checkinDescription = checkinDescription.Replace("![Testingnotes]", this.txtTestingNotes.Text.Replace(Environment.NewLine, "<BR>"));

                // Other comments notes
                checkinDescription = checkinDescription.Replace("![Othercomments]", this.txtOtherComments.Text.Replace(Environment.NewLine, "<BR>"));

                // Affected files
                checkinDescription = checkinDescription.Replace("![Files]", GetCheckinFilesInHtml());
            }

            return checkinDescription.ToString();
        }

        /// <summary>
        /// Get Check-in Notes.
        /// </summary>
        /// <returns>Returns Check-in Note object.</returns>
        private CheckinNote GetCheckinNotes()
        {
            var checkinNoteCodeReviewersField = new CheckinNoteFieldValue("1 - Code Reviewers", this.txtCodeReviewers.Text);
            var checkinNoteTestedByField = new CheckinNoteFieldValue("2 - Tested By", this.txtTestedBy.Text);
            var checkinNoteTestingNotesField = new CheckinNoteFieldValue("3 - Testing Notes", this.txtTestingNotes.Text);
            var checkinNoteOtherCommentsField = new CheckinNoteFieldValue("4 - Other Comments", this.txtOtherComments.Text);
            var checkinNoteExtraCommentsField = new CheckinNoteFieldValue("5 - Check-in using", "TFS Submit");

            return new CheckinNote(
                new[] 
                {
                    checkinNoteCodeReviewersField, 
                    checkinNoteTestedByField,
                    checkinNoteTestingNotesField,
                    checkinNoteOtherCommentsField,
                    checkinNoteExtraCommentsField,
                });
        }

        /// <summary>
        /// Send an email with the check-in information.
        /// </summary>
        /// <param name="description">Check-in description.</param>
        private void SendNotification(string description)
        {
            try
            {
                using (var mailMessage = new MailMessage())
                {
                    mailMessage.Subject = string.Format(CultureInfo.InvariantCulture, Resources.CheckinEmailSubjectString, GetWorkItemDescription().Replace(Environment.NewLine, "; "));
                    mailMessage.Body = description;

                    foreach (var domain in TfsSubmitConfig.DomainFullyQualifiedName.Split(new[] { ',', ';' }).Select(str => str.Trim()).Where(domain => domain.Length != 0))
                    {
                        using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
                        {
                            using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, Environment.UserName))
                            {
                                if (userPrincipal == null)
                                {
                                    continue;
                                }

                                mailMessage.From = new MailAddress(userPrincipal.EmailAddress);
                                mailMessage.IsBodyHtml = true;

                                if (this.txtEmail.Text.Length > 0)
                                {
                                    var emails = this.txtEmail.Text.Split(Utilities.NameSeparator);
                                    foreach (var email in emails)
                                    {
                                        mailMessage.To.Add(new MailAddress(email.Trim() + "@" + TfsSubmitConfig.MailDomainName));
                                    }
                                }

                                if (this.txtCCEmail.Text.Length > 0)
                                {
                                    var emails = this.txtCCEmail.Text.Split(Utilities.NameSeparator);
                                    foreach (var email in emails)
                                    {
                                        mailMessage.CC.Add(new MailAddress(email.Trim() + "@" + TfsSubmitConfig.MailDomainName));
                                    }
                                }

                                using (var smtp = new SmtpClient())
                                {
                                    smtp.Host = TfsSubmitConfig.SmtpServer;
                                    smtp.Credentials = CredentialCache.DefaultNetworkCredentials;

                                    // Send Email.
                                    smtp.Send(mailMessage);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            catch (SmtpException err)
            {
                MessageBox.Show(
                    err.Message,
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }
        }

        /// <summary>
        /// Email KeyDown event.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">Event arguments object.</param>
        private void TxtEmailKeyDown(object sender, KeyEventArgs e)
        {
            Utilities.ValidateUser(sender, e);
        }

        /// <summary>
        /// CodeReviewersD double click event.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">Event arguments object.</param>
        private void TxtCodeReviewersDoubleClick(object sender, EventArgs e)
        {
            // Open code review when change needs work.
            if (this.txtCodeReviewers.Text.StartsWith(MalevichDal.NeedsWorkConst, StringComparison.OrdinalIgnoreCase))
            {
                MalevichDal.OpenCodeReviewInBrowser(shelvesetName, workspace.Name);
            }
        }

        /// <summary>
        /// Update Email text with all WI owners.
        /// </summary>
        private void SetWorkItemOwnerInTheCheckinMailList()
        {
            // Build list of work item owners.
            var owners = new StringBuilder(this.txtEmail.Text);
            foreach (WorkItemCheckinInfo wi in workItemsCheckinInfo)
            {
                if (owners.Length > 0)
                {
                    owners.Append(Utilities.NameSeparator);
                }

                owners.Append(wi.WorkItem.CreatedBy);
            }

            this.txtEmail.Text = owners.ToString();
            Utilities.ValidateUser(this.txtEmail, new KeyEventArgs(Keys.Control | Keys.K));
        }
    }
}
